#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include "resource.h"

#define SCALE_STEP (0.25f)
#define OFFSET_STEP (0.025f)

typedef long double REAL;

typedef struct _appVars {
    double scaleStep;
    double offsetStep;
    int maxIterations;
} appVars, *pAppVars;

typedef struct _threadData {
    HANDLE handle;
    RECT renderArea;
    char active;
    char id;
} threadData, *pthreadData;

HINSTANCE hInst;
HWND hwin;
HDC hdc = 0, wdc = 0;
HWND hStatus = 0;
HBITMAP bmp = 0;
BITMAPINFO* bmpInfo;
BITMAPFILEHEADER* bmpheader;
BYTE *pixels = NULL;
POINTS mouse;
HACCEL keys;
char txt[30];
unsigned int width, height, maxIterations = 32;
REAL scale = 3.5f, offsetX = -0.5f, offsetY = 0.0f, bailout = 4.0f;


void Test(const UINT posX, const UINT posY, const UINT endX, const UINT endY)
{
    unsigned int i, j;
    for (i = posX, j = height-1; i < width; ++i)
    {
        pixels[i] = 0;			// Bottom
        pixels[width*j+i] = 0;	// Top
    }
    for (j = posY,i = width-1; j < height; ++j)
    {
        pixels[width*j] = 0;		// Left
        pixels[width*j+i] = 0;	// Right
    }
}


void Mandelbrot (const UINT posX, const UINT posY, const UINT endX, const UINT endY)
{
    const float color = 255.f / (float)maxIterations;
    const REAL term = scale / (width > height ? width : height);
    const REAL x0_pre = -term * width / 2 + offsetX;
    const REAL y0_pre = -term * height/ 2 + offsetY;

    unsigned int i, j;
    for (j = posY; j < endY; ++j)
    {
    	const REAL y0 = j * term + y0_pre;
    	int pos = width * j;
        for (i = posX; i < endX; ++i)
        {
            const REAL x0 = i * term + x0_pre;
            REAL x = x0;
            REAL y = y0;
            REAL x2 = x * x;
            REAL y2 = y * y;

            unsigned int iter = 0;
            while (x2 + y2 <= bailout && ++iter <= maxIterations)
            {
                y = 2 * x * y + y0;
                x = x2 - y2 + x0;
                x2 = x * x;
                y2 = y * y;
            }
            pixels[pos + i] = (iter > maxIterations) ? 255 : (BYTE)(color * (float)iter + 0.5f);
        }
    }
    Test(posX, posY, endX, endY);
}

int numberOfProcessors()
{
    SYSTEM_INFO siSysInfo;
    GetSystemInfo(&siSysInfo);
    return siSysInfo.dwNumberOfProcessors;
}

//threadData* hThreads;



inline void hThreadsInit ()
{

}

/*dividir
area total = sumar area de todos los RECTs
sub area = area total / numero de nucleos
cada rect dividir entre x de manera que que aproxime a sub area*/

void render()
{
}



void MandelbrotThreaded (const UINT posX, const UINT posY, const UINT endX, const UINT endY)
{

}

BOOL CALLBACK DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//printf("%d %d %d %d\n", hwnd, uMsg, wParam, lParam);
    switch(uMsg)
    {
    case WM_PAINT:
        printf("WM_PAINT\n");
        RECT r;
        if (GetUpdateRect(hwnd, &r, 0))
        {
            PAINTSTRUCT p;
            HDC dc = BeginPaint(hwnd, &p);
            if (!dc) return 0;
            BitBlt(dc, r.left, r.top, r.right-r.left, r.bottom-r.top, hdc, r.left, r.top, SRCCOPY);
            EndPaint(hwnd, &p);
        }
        return 0;

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
		case CMD_MOV_UP:
			offsetY += scale * OFFSET_STEP;
			goto render;

		case CMD_MOV_DOWN:
			offsetY -= scale * OFFSET_STEP;
			goto render;

		case CMD_MOV_LEFT:
			offsetX -= scale * OFFSET_STEP;
			goto render;

		case CMD_MOV_RIGHT:
			offsetX += scale * OFFSET_STEP;
			goto render;

		case CMD_ZOOM_INC:
			scale *= 1.f - SCALE_STEP;
			goto render;

		case CMD_ZOOM_DEC:
			scale *= 1.f + SCALE_STEP;
			goto render;

		case CMD_ITR_INC:
			maxIterations += 32;
			sprintf(txt, "Iter: %d", maxIterations);
			SendMessage(hStatus, SB_SETTEXT, 1, (LPARAM)txt);
			goto render;

		case CMD_ITR_DEC:
			maxIterations -= 32;
			sprintf(txt, "Iter: %d", maxIterations);
			SendMessage(hStatus, SB_SETTEXT, 1, (LPARAM)txt);
			goto render;

		case CMD_BAIL_INC:
			bailout += 4.0f;
			sprintf(txt, "Bail: %d", (unsigned int)bailout);
			SendMessage(hStatus, SB_SETTEXT, 2, (LPARAM)txt);
			goto render;

		case CMD_BAIL_DEC:
			bailout -= 4.0f;
			sprintf(txt, "Bail: %d", (unsigned int)bailout);
			SendMessage(hStatus, SB_SETTEXT, 2, (LPARAM)txt);
			goto render;

		case CMD_SAVE:
		{
			FILE *f = fopen("output.bmp", "wb");
			if (f)
			{
				fwrite(bmpheader, 1, sizeof(BITMAPFILEHEADER), f);
				fwrite(bmpInfo, 1, 1064, f);
				fwrite(pixels, 1, width*height, f);
				fclose(f);
				SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM)"Image saved");
			}
			else MessageBox(hwin, "Error saving imagen.", "Error", MB_ICONERROR);
			return 0;
		}
        case CMD_QUIT:
            EndDialog(hwnd, 0);
            return 0;

		case CMD_DEFPOS:
			scale = 3.5f, offsetX = -0.5f, offsetY = 0.0f, bailout = 4.0f;

		case CMD_RENDER:
		  render:
            Mandelbrot(0, 0, width, height);
            BitBlt(wdc, 0, 0, width, height, hdc, 0, 0, SRCCOPY);
            return 0;
		default: return TRUE;
        }

	case WM_KEYDOWN:
	{
		printf("KEY_DOWN: %d\t%d\n", (int)wParam, (int)lParam);
		return 0;
	}

    case WM_MOUSEMOVE:
        if(wParam & MK_LBUTTON)
        {
        	POINTS pos = MAKEPOINTS(lParam);
            int move_x = pos.x - mouse.x;
            int move_y = pos.y - mouse.y;

			const REAL term = scale / (width > height ? width : height);
			offsetX -= term * move_x;
			offsetY += term * move_y;

            int i, j;
            if (move_x > 0)
            {
                if (move_y > 0)
                {
                    for (i = 0; i < height-move_y ; ++i)
						for (j = width - 1; j >= move_x ; --j)
							pixels[i*width+j] = pixels[(i+move_y)*width+j-move_x];
					//BitBlt(hdc, move_x, move_y, width-move_x, height-move_y, hdc, 0, 0, SRCCOPY);
					Mandelbrot(move_x, height-move_y, width, height);
                }
                else if (move_y < 0)
                {
                    for (i = height - 1; i >= -move_y; --i)
						for (j = width - 1; j >= move_x ; --j)
							pixels[i*width+j] = pixels[(i+move_y)*width+j-move_x];
					//BitBlt(hdc, move_x, 0, width-move_x, height-move_y, hdc, 0, -move_y, SRCCOPY);
					Mandelbrot(move_x, 0, width, -move_y);
                }
                else
                {
                    for (i = 0; i < height; ++i)
						for (j = width - 1; j >= move_x ; --j)
							pixels[i*width+j] = pixels[i*width+j-move_x];
					//BitBlt(hdc, move_x, 0, width-move_x, height, hdc, 0, 0, SRCCOPY);
                }
                Mandelbrot(0, 0, move_x, height);
            }
            else if (move_x < 0)
            {
				if (move_y > 0)
                {
                    for (i = 0; i < height-move_y ; ++i)
						for (j = 0; j < width + move_x ; ++j)
							pixels[i*width+j] = pixels[(i+move_y)*width+j-move_x];
                	//BitBlt(hdc, move_x, move_y, width+move_x, height-move_y, hdc, 0, 0, SRCCOPY);
					Mandelbrot(0, height-move_y, width+move_x, height);
				}
                else if (move_y < 0)
                {
                    for (i = height - 1; i >= -move_y; --i)
						for (j = 0; j < width + move_x ; ++j)
							pixels[i*width+j] = pixels[(i+move_y)*width+j-move_x];
                	//BitBlt(hdc, move_x, 0, width+move_x, height-move_y, hdc, 0, -move_y, SRCCOPY);
					Mandelbrot(0, 0, width+move_x, -move_y);
                }
                else
                {
                    for (i = 0; i < height; ++i)
						memcpy(pixels+width*i, pixels+width*i - move_x, width+move_x);

                	//BitBlt(hdc, move_x, 0, width+move_x, height, hdc, 0, 0, SRCCOPY);
                }
                Mandelbrot(width+move_x-5, 0, width, height);
            }
            else
            {
				if (move_y > 0)
                {
                    memcpy(pixels, pixels + width * move_y, width * (height - move_y));
                	//BitBlt(hdc, 0, move_y, width, height-move_y, hdc, 0, 0, SRCCOPY);
					Mandelbrot(0, height-move_y, width, height);
                }
                else if (move_y < 0)
                {
                    for (i = height - 1; i >= -move_y ; --i)
						memcpy(pixels+width*i, pixels+width*(i+move_y), width);

                	//BitBlt(hdc, 0, 0, width, height-move_y, hdc, 0, -move_y, SRCCOPY);
					Mandelbrot(0, 0, width, -move_y);
                }
            }
            BitBlt(wdc, 0, 0, width, height, hdc, 0, 0, SRCCOPY);
            mouse = pos;
        }
        else if(wParam & MK_MBUTTON)
        {

        }
        else if(wParam & MK_RBUTTON)
        {

        }
        return TRUE;

    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
		mouse = MAKEPOINTS(lParam);
        return 0;

    case WM_MOUSEWHEEL:
    {
    	short delta = (short)HIWORD(wParam);
		SetStretchBltMode(wdc, COLORONCOLOR); //HALFTONE
		//SetBrushOrgEx(wdc, 0, 0, NULL);

    	if (delta > 0)
    	{
    	    StretchBlt(wdc, 0, 0, width, height, hdc, 64, 48, width-128, height-96, SRCCOPY);
    	}
		else
		{
		    StretchBlt(wdc, 64, 48, width-128, height-96, hdc, 0, 0, width, height, SRCCOPY);
		}


        mouse = MAKEPOINTS(lParam);
        //const REAL term = scale / 7.5f / (REAL)(width > height ? width : height);
        //offsetX -= term * (width/2 - mouse.x) / 2;
        //offsetY += term * (height/2 - mouse.y) / 2;

        scale *= (1.0f - SCALE_STEP * (float)delta/120.0f);
        if (scale < 0) scale = -scale;
        Mandelbrot(0, 0, width, height);
        BitBlt(wdc, 0, 0, width, height, hdc, 0, 0, SRCCOPY);
        return TRUE;
    }

    case WM_SIZE:
    {
        HWND hStatus = GetDlgItem(hwnd, STATUS_BAR);
        SendMessage(hStatus, WM_SIZE, 0, 0);
        RECT rcStatus;
        GetWindowRect(hStatus, &rcStatus);

        width = LOWORD(lParam) - LOWORD(lParam) % 4;
        height = HIWORD(lParam) - rcStatus.bottom + rcStatus.top;


        if (height < 1)
        {
            height = 1;
            return 0;
        }

        sprintf(txt, "Area: %d x %d", width, height);
		SendMessage(hStatus, SB_SETTEXT, 3, (LPARAM)txt);

        bmpInfo->bmiHeader.biWidth = width;
        bmpInfo->bmiHeader.biHeight = height;
        DeleteObject(bmp);
        bmp = CreateDIBSection(0, bmpInfo, DIB_RGB_COLORS, (void**)&pixels, NULL, 0);
        SelectObject(hdc, bmp);
        Mandelbrot(0, 0, width, height);
        BitBlt(wdc, 0, 0, width, height, hdc, 0, 0, SRCCOPY);
        return 0;
    }

    case WM_INITDIALOG:
    {
        hwin = hwnd;
        wdc = GetDC(hwnd);

		bmpheader = malloc(sizeof(BITMAPFILEHEADER));
		bmpheader->bfSize = sizeof(BITMAPFILEHEADER);
		bmpheader->bfType = 0x4D42;
		bmpheader->bfReserved1 = 0;
		bmpheader->bfReserved2 = 0;
		bmpheader->bfOffBits = sizeof(BITMAPFILEHEADER) + 1064; //BITMAPINFOHEADER + palete(256*4)

        bmpInfo = malloc(1064);
        bmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        bmpInfo->bmiHeader.biWidth = width;
        bmpInfo->bmiHeader.biHeight = height;
        bmpInfo->bmiHeader.biPlanes = 1;
        bmpInfo->bmiHeader.biBitCount = 8;
		bmpInfo->bmiHeader.biCompression = BI_RGB;
		bmpInfo->bmiHeader.biSizeImage = width*height;
		bmpInfo->bmiHeader.biXPelsPerMeter = 0;
		bmpInfo->bmiHeader.biYPelsPerMeter = 0;
        bmpInfo->bmiHeader.biClrUsed = 255;
        bmpInfo->bmiHeader.biClrImportant = 256;

        RGBQUAD* pal = bmpInfo->bmiColors;
        int i, val = 0;
        for(i = 0; i < 43; i++)
        	pal[i] = (RGBQUAD){0, val += 6, 255, 0};
        for(i = 43; i < 85; i++)
        	pal[i] = (RGBQUAD){0, 255, 255 - (val -= 6), 0};
        for(i = 85; i < 127; i++)
        	pal[i] = (RGBQUAD){val += 6, 255, 0, 0};
        for(i = 127; i < 170; i++)
        	pal[i] = (RGBQUAD){255, 255 - (val -= 6), 0, 0};
        for(i = 170; i < 212; i++)
        	pal[i] = (RGBQUAD){255, 0, val += 6, 0};
        for(i = 212; i < 255; i++)
        	pal[i] = (RGBQUAD){255 - (val -= 6), 0, 255, 0};

		pal[255] = (RGBQUAD){0, 0, 0, 0};

        hdc = CreateCompatibleDC(0);
		keys = LoadAccelerators(hInst, MAKEINTRESOURCE(ACCEL_KEYS));

        hStatus = GetDlgItem(hwnd, STATUS_BAR);
		int statwidths[] = {150, 220, 290, 400, -1};

		SendMessage(hStatus, SB_SETPARTS, 5, (LPARAM)statwidths);

		sprintf(txt, "Iter: %d", maxIterations);
		SendMessage(hStatus, SB_SETTEXT, 1, (LPARAM)txt);
		sprintf(txt, "Bail: %d", (unsigned int)bailout);
		SendMessage(hStatus, SB_SETTEXT, 2, (LPARAM)txt);

        return TRUE;
    }

    case WM_CLOSE:
        EndDialog(hwnd, 0);
        return TRUE;

    case WM_DESTROY:
        if (hdc) DeleteDC(hdc);
        if (wdc) ReleaseDC(hwin, wdc);
        if (bmp) DeleteObject(bmp);
        PostQuitMessage(0);
        return TRUE;
    }

    return FALSE;
}


int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    hInst = hInstance;
    return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DialogProc);
}

/*
	const double startTime = GetTickCount();
	const double elapsedTime = (GetTickCount() - startTime) / 1000;
	printf("Time: %.3f s\n",elapsedTime);

	sprintf(txt, "Render time: %d s", bailout);
	SendMessage(hStatus, SB_SETTEXT, 3, (LPARAM)txt);
*/
